using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure.StorageClient.Protocol;

namespace Microsoft.WindowsAzure.Accelerator
{
    /// <summary>
    /// Extensions to the CloudStorageAccount class, which represents a Windows Azure storage account.
    /// </summary>
    public static class CloudStorageAccountExtensions
    {
        /// <summary>
        /// Gets the next writable drive.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <param name="driveUri">The drive URI.</param>
        /// <param name="cacheSizeInMB">The cache size in MB.</param>
        /// <param name="createSizeInMB">The create size in MB.</param>
        /// <returns></returns>
        public static CloudDrive GetDrive(this CloudStorageAccount account, String driveUri, Int32 cacheSizeInMB, Int32 createSizeInMB)
        {
            CloudDrive drive = account.CreateCloudDrive(driveUri);
            if (createSizeInMB > 0 && !account.VerifyResource(driveUri))
            {
                drive.Protect(d => d.Create(cacheSizeInMB));
            }
            try
            {
                drive.Mount(cacheSizeInMB, DriveMountOptions.None);
                return drive;
            }
            catch (CloudDriveException ex)
            {
                Trace.TraceError("Failed to mount the CloudDrive:  {0}", ex.Message);
            }
            return account.GetCloneDrive(drive, cacheSizeInMB);
        }

        /// <summary>
        /// Gets the next writable drive.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <param name="drive">The drive.</param>
        /// <param name="cacheSizeInMB">The cache size in MB.</param>
        /// <returns></returns>
        public static CloudDrive GetCloneDrive(this CloudStorageAccount account, CloudDrive drive, Int32 cacheSizeInMB)
        {
            Uri snapshotUri = null;
            Int32 counter = 1;
            do
            {
                String instanceUri = String.Format("{0}.{1}", drive.Uri, counter++);
                if (!account.VerifyResource(instanceUri))
                {
                    try
                    {
                        //i| Attempt to create a snapshot of the original drive the first time only.
                        if (snapshotUri == null)
                            snapshotUri = drive.Snapshot();
                        CloudDrive snapDrive = account.CreateCloudDrive(snapshotUri.AbsoluteUri);
                        snapDrive.CopyTo(new Uri(instanceUri));
                    }
                    catch (Exception ex)
                    {
                        Trace.TraceError("Failed to create copy of CloudDrive:  {0}.", ex.Message);
                    }
                }
                try
                {
                    //i| Attempt to mount.
                    CloudDrive cd = account.CreateCloudDrive(instanceUri);
                    //x| cd.Protect(d => d.Create(cacheSizeInMB));
                    cd.Mount(cacheSizeInMB, DriveMountOptions.None);
                    return cd;
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Failed to mount the CloudDrive:  {0}", ex.Message);
                }
            } while (counter < 4);
            return null;
        }

        /// <summary>
        /// Downloads to blob to a lcoal file.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <param name="blobUri">The BLOB URI.</param>
        /// <param name="localPath">The local path.</param>
        /// <param name="overwriteFileIfExists">if set to <c>true</c> [overwrite file if exists].</param>
        /// <returns></returns>
        public static void DownloadToFile(this CloudStorageAccount account, String blobUri, String localPath, Boolean overwriteFileIfExists)
        {
            //b| Bug: should throw the appropriate and meaningful argument exception.
            if (account == null || String.IsNullOrEmpty(blobUri) || String.IsNullOrEmpty(localPath))
                return;
            if (File.Exists(localPath) && !overwriteFileIfExists)
                throw new IOException("File already exists and overwrite flag set to false.");
            File.Delete(localPath);
            CloudBlob cloudBlob = account.CreateCloudBlobClient().GetBlobReference(blobUri);
            cloudBlob.DownloadToFile(localPath); //i| Let the cloud blob class throw the exception.
        }

        /// <summary>
        /// Downloads the blob file as a string.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <param name="blobUri">The BLOB URI.</param>
        /// <returns></returns>
        public static String DownloadText(this CloudStorageAccount account, String blobUri)
        {
            //b| Bug: should throw the appropriate and meaningful argument exception.
            if (account == null || String.IsNullOrEmpty(blobUri))
                return null;
            CloudBlob cloudBlob = account.CreateCloudBlobClient().GetBlobReference(blobUri);
            using (var ms = new MemoryStream())
            {
                cloudBlob.DownloadToStream(ms);
                ms.Seek(0, SeekOrigin.Begin);
                using (var sr = new StreamReader(ms))
                    return sr.ReadToEnd();
            }
        }

        /// <summary>
        /// Determines whether Uri refers to a valid and existing blob.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <param name="blobUri">The Uri for the blob.</param>
        /// <returns>
        /// 	<c>true</c> if is valid BLOB resource for the specified account; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean VerifyResource(this CloudStorageAccount account, String blobUri)
        {
            return account.CreateCloudBlobClient().GetBlobReference(blobUri).Exists();
        }

        /// <summary>
        /// Uploads the file to blob storage.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <param name="localPath">The local path.</param>
        /// <param name="blobUri">The BLOB URI.</param>
        /// <param name="createContainerIfNotExists">if set to <c>true</c> [create container if not exists].</param>
        /// <param name="overwriteBlobIfExists">if set to <c>true</c> [overwrite BLOB if exists].</param>
        /// <returns></returns>
        public static void UploadFile(this CloudStorageAccount account, String localPath, String blobUri, Boolean createContainerIfNotExists, Boolean overwriteBlobIfExists)
        {
            //b| Bug: should throw the appropriate and meaningful argument exception.
            if (account == null || !File.Exists(localPath) || String.IsNullOrEmpty(blobUri))
                return;
            CloudBlob blob = account.CreateCloudBlobClient().GetBlobReference(blobUri);
            if (createContainerIfNotExists)
                blob.Container.CreateIfNotExist();
            if (overwriteBlobIfExists)
                blob.DeleteIfExists();
            blob.UploadFile(localPath);
        }

        /// <summary>
        /// Uploads the VHD.
        /// </summary>
        /// <param name="account">The account.</param>
        /// <param name="localPath">The local path.</param>
        /// <param name="uploadUrl">The upload URL.</param>
        /// <param name="overwriteIfExists">Overwrite if exists.</param>
        public static void UploadVhd(this CloudStorageAccount account, String localPath, String uploadUrl, Boolean overwriteIfExists)
        {
            const Int32 _1KB = 1024;
            const Int32 _1MB = _1KB * _1KB;
            const Int32 _4MB = 4 * _1MB;

            const Int32 _PAGESIZE = 512;
            const Int32 _BUFFERSIZE = _4MB;
            const Int32 _BUFFERPAGES = _BUFFERSIZE / _PAGESIZE;

            CloudPageBlob blob = account.CreateCloudBlobClient().GetPageBlobReference(uploadUrl);
            if (blob.Exists() && !overwriteIfExists)
                throw new TargetException("The page blob already exists and forced overwrite set to false.");
            blob.Container.CreateIfNotExist();

            Func<Int64, Int64> roundToNearest = (currentSize) => (Int32)Math.Ceiling((double)currentSize / _PAGESIZE) * _PAGESIZE;
            Int64 localSize = new FileInfo(localPath).Length;
            Int64 roundedSize = (Int32)Math.Ceiling((double)localSize / _PAGESIZE) * _PAGESIZE;
            Int64 roundedUpSize = roundToNearest(new FileInfo(localPath).Length);
            Int32 roundedUpMB = (Int32)(roundedUpSize / _1MB);
            Console.WriteLine("Total size of upload:  {0} Mb", roundedUpSize / _1MB);

            blob.Create(roundedUpSize);

            FileStream stream = File.OpenRead(localPath);
            Byte[] buffer = new Byte[_BUFFERSIZE];
            Int32 bytesRead;
            Int64 vhdOffset = 0;

            // read 4MB at a time
            while ((bytesRead = stream.Read(buffer, 0, _BUFFERSIZE)) > 0)
            {
                if (vhdOffset > 0 && vhdOffset < localSize)
                    Console.WriteLine("\r\n{0,4} of {1} Mb ( {2,5:P2} )", vhdOffset / _1MB, roundedUpMB, ((Double)vhdOffset / roundedUpSize));

                // fill the remainder (if any) with zeros
                for (Int32 i = bytesRead; i < _BUFFERSIZE; i++)
                {
                    buffer[i] = 0;
                }

                Int32 offsetToTransfer = 0;
                Int32 sizeToTransfer = 0;
                for (Int32 page = 0; page < _BUFFERPAGES; page++)
                {
                    //i| test whether entire page is empty
                    Boolean empty = true;
                    for (Int32 i = 0; i < _PAGESIZE; i++)
                    {
                        if (buffer[page * _PAGESIZE + i] != 0)
                        {
                            empty = false;
                            break;
                        }
                    }

                    //i| if not empty, include the page in our next transfer
                    if (!empty)
                    {
                        sizeToTransfer += _PAGESIZE;
                        //Console.Write("-");
                    }
                    else
                    {
                        //Console.Write("+");
                        //i| transfer everything we have so far
                        if (sizeToTransfer > 0)
                        {
                            blob.WritePages(new MemoryStream(buffer, offsetToTransfer, sizeToTransfer), vhdOffset);
                            vhdOffset += sizeToTransfer;
                        }
                        //i| start the next transfer after this page
                        sizeToTransfer = 0;
                        offsetToTransfer = (page + 1) * _PAGESIZE;
                        //i| increment where we are in the VHD by a page
                        vhdOffset += _PAGESIZE;
                    }
                }

                // transfer whatever we have left at the end of the buffer
                if (sizeToTransfer > 0)
                {
                    blob.WritePages(new MemoryStream(buffer, offsetToTransfer, sizeToTransfer), vhdOffset);
                    vhdOffset += sizeToTransfer;
                }
            }

            //i| Success
            Console.WriteLine("Blob successfully uploaded.");

            //i| Create snapshot.
            var snapshot = blob.CreateSnapshot();
            if (snapshot.SnapshotTime != null)
                Console.WriteLine("Snapshot created:  '{0}'", BlobRequest.Get(snapshot.Uri, 0, snapshot.SnapshotTime.Value.ToUniversalTime(), null).RequestUri.AbsoluteUri);
            else
                Console.WriteLine("Unable to create snapshot of uploaded page blob.");
        }

    }
}